package sve;

import java.text.DecimalFormat;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import sve.FactorHandler.Factor;
import sve.generator.BoolDecVar;

import xadd.XADD;
import xadd.XADD.ArithExpr;
import xadd.XADD.DoubleExpr;
import cmdp.HierarchicalParser;
import cmdp.TokenStream;

public class SVE {
	private Buffer _buffer;

	public void load(String filename) {
		_buffer = Buffer.parse(filename + ".sve");
		// test();

		// _buffer.getContext().getGraph(_buffer.getVar("o1")).launchViewer();
		// int id = _buffer.getVar("x1");
		// //id = _buffer.getContext().computeDefiniteIntegral(id, "x1");
		// //HashMap<String, Double> hm = new HashMap<String, Double>();
		// HashMap<String, ArithExpr> hm = new HashMap<String, ArithExpr>();
		// //ArrayList<String> vars = new ArrayList<String>( Arrays.asList(new String[] {"d"}));
		// hm.put("x1", new XADD.DoubleExpr(5d));
		// //System.out.println(_buffer.getContext().evaluate(id, new HashMap<String, Boolean>(), hm));
		// System.out.println(_buffer.getContext().getString(_buffer.getContext().substitute(id, hm)));
		// System.exit(1);

		// _buffer.getContext().getGraph(_buffer.getVar("x1")).launchViewer();
		// _buffer.getContext().getGraph(_buffer.getVar("d")).launchViewer();
		// int a = _buffer.getContext().apply(_buffer.getVar("x1"), _buffer.getVar("d"), XADD.PROD);
		// _buffer.getContext().getGraph(a).launchViewer();

		// _buffer.getContext().getGraph(_buffer.getVar("x1")).launchViewer();
		// Converter.toString(generateEvaluationDouble(_buffer.getContext(), _buffer.getVar("x1"), "x1", 0, .1, 10),
		// ",","\n");
	}

	private void test() {
		int id = _buffer.getVar("o1");
		// id = _buffer.getContext().substitute(id, hm);
		HashMap<String, Double> hm = new HashMap<String, Double>();
		HashMap<String, Boolean> bool_assign = new HashMap<String, Boolean>();
		double[][] val = new double[1000][2];
		bool_assign.put("b1", false);
		HashMap<String, XADD.ArithExpr> subst = new HashMap<String, XADD.ArithExpr>();
		subst.put("x1", new DoubleExpr(1.));
		// subst.put("b1", new XADD.(true));
		_buffer.getContext().substitute(id, subst);

		do {
			double step = 0;
			int idx = 0;

			bool_assign.put("b1", !bool_assign.get("b1"));
			while (idx < val[0].length / 2) {
				hm.clear();
				val[idx][0] = step;
				hm.put("o1", step);
				Double d = _buffer.getContext().evaluate(id, bool_assign, hm);
				val[idx][1] = d == null ? 0 : d;
				step = step + .1;
				idx++;
			}
		} while (bool_assign.get("b1"));
		String str = Converter.toString(val, ",", "\n");
		Utils.print("./mat-script/eval_o1_x1b1.csv", str);
		System.exit(1);
	}

	public static int getSingleXADD(XADD x, double val) {
		return getSingleXADD(x, new DecimalFormat("#.########################").format(val));
	}

	public static int getSingleXADD(XADD x, String val) {
		ArrayList a = new ArrayList();
		a.add("[" + val + "]");
		return x.buildCanonicalXADD(a);
	}

	public Pair<Integer, XADD> solveP(String[] query, String[] evidence) {
		return solveP(query, evidence, null, null);
	}

	public static int getXADD(XADD x, String str) {
		TokenStream t = new TokenStream();
		t.read(str);
		ArrayList a = HierarchicalParser.ParseFileInt(t, 0);
		return x.buildCanonicalXADD((ArrayList) a.get(0));
	}

	// assumes the solveP function has been previously called
	public Pair<Integer, XADD> EP(String query, Pair<Integer, XADD> p) {
		int id = p.getKey();
		XADD x = p.getValue();
		XADD newX = new XADD();
		id = getXADD(newX, x.getString(id, false));
		id = newX.apply(id, getSingleXADD(newX, query), XADD.PROD);
		Utils.print("./mat-script/xpx.txt", "xp(x)", id, newX);
		// System.out.println("----------------------------");
		// System.out.println(x.getString(id));
		// System.out.println("----------------------------");
		id = newX.computeDefiniteIntegral(id, query);
		p.setKey(id);
		p.setValue(newX);

		return p;
	}

	public Pair<Integer, XADD> EP(String query, String[] evidence, String[] evals, double[] vals) {
		Pair<Integer, XADD> p = solveP(new String[] { query }, evidence, evals, vals);
		return EP(query, p);
	}

	public Pair<Integer, XADD> solveP(String[] query, String[] evidence, String[] vars, double[] vals) {
		return solveP(query, evidence, vars, vals, true);
	}

	private int substitute(int id, String[] vars, double[] vals) {
		if (vars == null || vals == null)
			return id;

		String s = _buffer.getContext().getString(id);
		for (int i = 0; i < vars.length; i++) {
			s = s.replace(vars[i], Double.toString(vals[i]));
		}

		return getXADD(_buffer.getContext(), s);
	}

	public Pair<Integer, XADD> solveP(String[] query, String[] evidence, String[] vars, double[] vals,
			boolean conditional) {
		Pair<Integer, XADD> solution = null;
		ArrayList<ArrayList<Integer>> q = _buffer.getIds(query), e = _buffer.getIds(evidence);
		int qidx = 0;
		int qID = _buffer.getVarId(query[qidx]);
		XADD qXADD = _buffer.getContext();

		if (vars != null && vals != null) {
			HashMap<String, ArithExpr> subst = new HashMap<String, ArithExpr>();
			for (int i = 0; i < vars.length; i++) {
				if (vars[i] == null)
					continue;
				subst.clear();
				subst.put(vars[i], new DoubleExpr(vals[i]));
				int id = qXADD.substitute(_buffer.getVarId(vars[i]), subst);
				_buffer.addVar(id);
				_buffer.removeVar(_buffer.getVarId(vars[i]));
			}
		}

		FactorHandler all_factors = FactorHandler.newInstance(_buffer);
		StringBuilder factor_log = new StringBuilder();
		pruneFactors(all_factors, evidence, query);
		for (String var : all_factors.collectVars()) {
			if (all_factors.getFactors().size() <= 1) { // current_factor == null ||
				break;
				// System.err.println("Where is this parameter [" + var + "] in " + all_factors);
			}

			Factor current_factor = all_factors.getFactors().get(0); // .getSingleFactor(var);

			if (current_factor == null && (Utils.contains(var, evidence) || Utils.contains(var, query))) {
				continue; // previously multiplied
			}
			int current_id = current_factor.id; // _buffer.getVar(var);
			for (Factor f : all_factors.getReferenceFactors(var)) {
				current_id = !f.var.equals(var) ? qXADD.apply(f.id, current_id, XADD.PROD) : current_id;
			}
			if (!(Utils.contains(var, query) || Utils.contains(var, evidence))) { // )
				if (_buffer.isBoolean(var)) {
					int restrict_high = qXADD.opOut(current_id, qXADD.getBoolVarIndex(var), XADD.RESTRICT_HIGH);
					int restrict_low = qXADD.opOut(current_id, qXADD.getBoolVarIndex(var), XADD.RESTRICT_LOW);
					current_id = qXADD.apply(restrict_high, restrict_low, XADD.SUM);
					factor_log.append("\n>>>  boolean Operation: \n" + _buffer.getContext().getString(current_id));
				} else {
					current_id = qXADD.computeDefiniteIntegral(current_id, var);
					factor_log.append("\n>>> integral: \n" + _buffer.getContext().getString(current_id));
				}
				// all_factors.remove(all_factors.getSingleFactor(var));
			}
			all_factors.update(var, current_id);
		}

		qID = all_factors.getReferenceFactors(query[0]).get(0).id;
		Utils.print("./mat-script/final_val.txt", qXADD.getString(qID, false));
		System.out.println("**********************************************");
		System.out.println(qXADD.getString(qID));
		System.out.println("**********************************************");
		// qXADD.getGraph(qID).launchViewer();

		if (conditional) {
			for (int i = 0; i < query.length; i++) {
				int z = qXADD.computeDefiniteIntegral(qID, query[i]);
				double d = getDouble(qXADD, z);
				qID = qXADD.apply(qID, getSingleXADD(qXADD, 1. / d), XADD.PROD);
			}
		}

		solution = new Pair<Integer, XADD>(qID, qXADD);

		return solution;
	}

	public Pair<Integer, XADD> integral(int id, String v) {
		id = _buffer.getContext().computeDefiniteIntegral(id, v);
		return new Pair<Integer, XADD>(id, _buffer.getContext());
	}

	private int infer(String[] query, String[] evidence, int qID, XADD qXADD, FactorHandler all_factors,
			StringBuilder factor_log) {
		ArrayList<Integer> traversed = new ArrayList<Integer>();
		// ArrayList<String> s = all_factors.getSortedVars(true);
		try {
			for (int i = 0; i < evidence.length + query.length; i++) {
				String var;
				if (i < query.length) {
					var = all_factors.getSingleFactor(query[i]).var; // s.get(i); // _buffer.getVar(varId);
				} else {
					var = all_factors.getSingleFactor(evidence[i - query.length]).var;
				}
				int current_var_id = _buffer.getVarId(var);
				// AbstractCollection<Factor> factors = ff.getContainingFactors(all_factors, var);

				if (_buffer.getDeltaId(var) > 0) { // it is a delta variable, lets perfrom delta operation{
					factor_log.append("\n<<< " + var + "[" + current_var_id + "] >>> \nReferenced Factors"
							+ all_factors);

					current_var_id = computeDelta(qXADD, var, current_var_id, factor_log, all_factors);
					Utils.print("./mat-script/factors.txt", factor_log.toString());
				} else { // it is a continuous variable

					current_var_id = computeContiniousFactor(qXADD, var, current_var_id, factor_log, all_factors,
							query, evidence, false);
					// _buffer.updateVar(var, current_var_id);
					Utils.print("./mat-script/after_integral.txt", qXADD.getString(current_var_id, false));
				}
				qID = current_var_id; // (qID > 0 && qID != current_var_id) ? qXADD.apply(qID, current_var_id,
										// XADD.PROD)
										// : current_var_id;
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return qID;
	}

	private void pruneFactors(FactorHandler all_factors, String[] evidence, String[] query) {
		ArrayList<Factor> factor_col = new ArrayList<FactorHandler.Factor>();
		ArrayList<String> ss = new ArrayList<String>();
		getVars(evidence, ss, all_factors);
		getVars(query, ss, all_factors); // Arrays.asList(

		for (Factor f : all_factors.getFactors()) {
			if (Utils.contains(f.var, ss) || Utils.contains(f.var, ss)) {
				factor_col.add(f);
			}
		}
		// for (int i = 0; i < evidence.length + query.length; i++) {
		// String v;
		// if (i < query.length)
		// v = query[i];
		// else
		// v = evidence[i - query.length];
		// for (Factor f : all_factors.collectVars(v)) {
		// if (!factor_col.contains(f)) {
		// factor_col.add(f);
		// }
		// }
		// }
		all_factors.clearFactors();
		all_factors.addAll(factor_col);
	}

	private void getVars(String[] vars, ArrayList<String> list, FactorHandler all_factors) {
		// ArrayList<String> ss = new ArrayList<String>();

		for (int i = 0; i < vars.length; i++) {
			String v = vars[i];
			if (v.length() == 0)
				return;
			// HashSet<String> hs =
			Collection<String> hs = _buffer.getContext().collectVars(_buffer.getVar(v)); //
			// Factor.getVars(all_factors.getParentChild(v));
			// //
			// //
			// Collection<String> hs = Factor.getVars(all_factors.getReferenceFactors(v));
			list.addAll(hs);
			hs.removeAll(Arrays.asList(v));
			Utils.removeIntersection(hs, list);
			getVars(hs.toArray(new String[0]), list, all_factors);
		}
	}

	private int computeContiniousFactor(XADD x, String var, int parent, StringBuilder factor_log,
			FactorHandler all_factors, String[] query, String[] evidence, boolean evidenceReached) {

		if (_buffer.getDeltaId(var) > 0) {
			return parent;
		}

		// ArrayList<Factor> refVariables = all_factors.getReferenceFactors(var); //
		// _buffer.getRefVariablesInOriginalNet(var);

		int t = -1, prod = -1; // / parent;
		int current_id = _buffer.getVar(var);
		ArrayList<Factor> to_remove = new ArrayList<FactorHandler.Factor>();
		Factor singleFactor = all_factors.getSingleFactor(var);
		to_remove.add(singleFactor);
		if (singleFactor != null) {
			ArrayList<Factor> factor_col;
			factor_col = all_factors.collectVars(var);
			// if (evidenceReached || Utils.contains(var, evidence) || (Utils.contains(var, query))) {
			// // new ArrayList<FactorHandler.Factor>(); // all_factors.collectVars(var);
			// // factor_col.addAll(all_factors.getSingleFactors(all_factors.getSingleFactor(var).factorVars));
			// } else {
			// factor_col = all_factors.getParentChild(var);
			// }
			for (Factor f : factor_col) { // singleFactor.factorVars) {
				if (f == null)
					continue;
				if (!f.var.equals(var)) {
					all_factors.getFactors().removeAll(to_remove);
					current_id = computeContiniousFactor(x, f.var, current_id, factor_log, all_factors, query,
							evidence, Utils.contains(var, evidence));
				} else {
					prod = prod > 0 && prod != current_id ? x.apply(prod, current_id, XADD.PROD) : current_id;
					current_id = prod;
				}
				Utils.print("./mat-script/prod.txt", x.getString(prod, false));
				// to_remove.add(all_factors.get(f.var, var));
			}
		}

		current_id = (parent != current_id && parent != _buffer.getVar(var)) ? x.apply(parent, current_id, XADD.PROD)
				: current_id;
		if (!(Utils.contains(var, query)) || Utils.contains(var, evidence)) { // )
			if (_buffer.isBoolean(var)) {
				int restrict_high = x.opOut(current_id, x.getBoolVarIndex(var), XADD.RESTRICT_HIGH);
				int restrict_low = x.opOut(current_id, x.getBoolVarIndex(var), XADD.RESTRICT_LOW);
				current_id = x.apply(restrict_high, restrict_low, XADD.SUM);
				factor_log.append("\n>>>  boolean Operation: \n" + _buffer.getContext().getString(current_id));
				// _buffer.updateVar(var, current_id);
			} else {
				current_id = x.computeDefiniteIntegral(current_id, var);
				factor_log.append("\n>>> integral: \n" + _buffer.getContext().getString(current_id));
			}
		}

		all_factors.update(var, current_id);

		return current_id;
	}

	private int computeDelta(XADD x, String var, int parent, StringBuilder factor_log, FactorHandler all_factors) {
		int current_id = _buffer.getVar(var);

		if (_buffer.getDeltaId(var) < 0)
			return parent;

		if (_buffer.getDeltaId(var) > 0) {
			ArrayList<Factor> to_remove = new ArrayList<FactorHandler.Factor>();

			for (Factor f : all_factors.getFactors(var)) {
				to_remove.add(f);
				for (int i = 0; i < f.factorVars.size(); i++) {
					to_remove.add(all_factors.get(f.factorVars.get(i), var));
					current_id = computeDelta(x, f.factorVars.get(i), current_id, factor_log, all_factors);
				}
			}
			all_factors.getFactors().removeAll(to_remove);
		}

		if (parent != _buffer.getVar(var)) {
			// current_id = _buffer.getVar(var);
			current_id = x.reduceProcessXADDLeaf(current_id, x.new DeltaFunctionSubstitution(var, parent), true);
			factor_log.append("\n>>> delta Operation over variable ( " + var + " ): \n"
					+ _buffer.getContext().getString(current_id));
		} else {
			all_factors.update(var, current_id);
		}

		return current_id;
	}

	private int computeDelta(XADD qXADD, String var, int current_id, ArrayList<Factor> all_factors,
			StringBuilder factor_log) {
		int t = -1;
		AbstractCollection<String> factors = _buffer.getContext().collectVars(current_id);
		for (String v : factors) {
			if (v.equals(var) || _buffer.getId(v) > 0)
				continue;
			t = qXADD
					.reduceProcessXADDLeaf(_buffer.getVar(v), qXADD.new DeltaFunctionSubstitution(v, current_id), true);
			factor_log.append("\n>>> delta Operation over variable ( " + v + " ): \n"
					+ _buffer.getContext().getString(current_id)); // current_var_id

			_buffer.addVar(t);
			// _buffer.updateVar(v, t);
		}
		// current_var_id = (t > 0) ? t : current_var_id;
		// _buffer.updateVar(var, current_var_id);
		return t;
	}

	private int getProducts(XADD qXADD, ArrayList<Integer> traversed, String var, AbstractCollection<String> factors,
			int factor_id, int operation) {
		int t = -1;
		System.out.println("Variable " + var + "-----------------------------------------------");
		for (String v : factors) { // This should be done recursively
			t = _buffer.getVarId(v);
			// if (traversed.contains(t))
			// continue;
			traversed.add(t);
			System.out.println(qXADD.getString(t));
			// if (operation == 1) {
			// t = qXADD.reduceProcessXADDLeaf(factor_id, qXADD.new DeltaFunctionSubstitution(var, t), true);
			// }
			factor_id = (t > -1) ? qXADD.apply(t, factor_id, XADD.PROD) : factor_id;
			System.out.println("-----------------------------------------------");
		}

		// if (operation == 1) {
		// if (_buffer.getDeltaId(var) > 0 && factors.get(1).size() == 0
		// && _buffer.getContext().collectVars(_buffer.getDeltaId(var)).size() > 0) {
		// t = qID;
		// factor_id = qXADD.reduceProcessXADDLeaf(factor_id, qXADD.new DeltaFunctionSubstitution(var, t), true);
		// }
		// }

		return factor_id;
	}

	private double getDouble(XADD qXADD, int z) {
		String s = qXADD.getString(z);
		s = s.replace("(", "");
		s = s.replace(")", "");
		s = s.replace("[", "");
		s = s.replace("]", "");

		return Double.parseDouble(s.trim());
	}

	private void checkVarsInStack(String var, ArrayList<String> stack, HashSet<String> cv) {
		int idx = Integer.MAX_VALUE;
		for (String ss : cv) {
			if (!ss.equals(var)) {
				int id = _buffer.getId(ss);
				checkVarsInStack(ss, stack, _buffer.getContext().collectVars(id));
				id = _buffer.getDeltaId(ss);
				checkVarsInStack(ss, stack, _buffer.getContext().collectVars(id));
			}
			if (!stack.contains(ss) && !ss.equals(var)) {
				stack.add(ss);
				idx = stack.indexOf(ss);
			}
		}
		if (stack.contains(var))
			stack.remove(var);
		stack.add(idx == Integer.MAX_VALUE ? 0 : idx, var);
	}

	private void evaluate(String[] query, String[] evals, double[] vals) {
		// _buffer.getContext().getGraph(1).launchViewer();
		HashMap<String, Double> cont_assign = new HashMap<String, Double>();
		HashMap<String, ArithExpr> subst = new HashMap<String, ArithExpr>();
		int id = -1;

		for (int i = 0; i < evals.length; i++) {
			cont_assign.put(evals[i], vals[i]);
			subst.put(evals[i], new DoubleExpr(vals[i]));
			id = (i == 0) ? _buffer.getVarId(evals[0]) : _buffer.getContext().apply(_buffer.getVarId(evals[i]), id,
					XADD.PROD);
		}

		id = _buffer.getContext().substitute(id, subst);
		_buffer.getContext().reduce(id);
		_buffer.getContext().getGraph(id).launchViewer();

		System.out.println(_buffer.getContext().getString(id));

		// System.out.println(_buffer.getContext().evaluate(id, new HashMap<String, Boolean>(), cont_assign));

		// HashMap<String, Double> cont_assign = new HashMap<String, Double>();
		// double conditional = 1;
		// for (int i = 0; i < evals.length; i++) {
		// cont_assign.put(evals[i], vals[i]);
		// conditional = conditional
		// * _buffer.getContext().evaluate(_buffer.getVarId(evals[i]), new HashMap<String, Boolean>(),
		// cont_assign);
		// cont_assign.clear();
		// }
		//
		// for (int i = 0; i < evals.length; i++) {
		//
		// }
	}

	public void evaulateProbIntegrity(String nodeVar, String var, String[] vars, double[] vals) {
		evaulateProbIntegrity(_buffer.getVar(nodeVar), var, vars, vals);
	}

	public void evaulateProbIntegrity(String nodeVar, String var) {
		evaulateProbIntegrity(_buffer.getVar(nodeVar), var, null, null);
	}

	public void evaulateProbIntegrity(int nodeId, String var, String[] vars, double[] vals) {
		evaulateProbIntegrity(nodeId, _buffer.getContext(), var, vars, vals);
	}

	public static void evaulateProbIntegrity(Pair<Integer, XADD> p, String var, String[] vars, double[] vals) {
		evaulateProbIntegrity(p.getKey(), p.getValue(), var, vars, vals);
	}

	public static void evaulateProbIntegrity(int id, XADD x, String var, String[] vars, double[] vals) {
		HashMap<String, ArithExpr> cont_assign = getSubstitutionList(vars, vals);
		id = x.substitute(id, cont_assign);
		id = x.computeDefiniteIntegral(id, var);
		Utils.print("evaulateProbIntegrity", id, x);
	}

	public static HashMap<String, ArithExpr> getSubstitutionList(String[] vars, double[] vals) {
		HashMap<String, ArithExpr> subst = new HashMap<String, XADD.ArithExpr>();
		if (vars != null && vals != null) {
			for (int i = 0; i < vars.length; i++) {
				subst.put(vars[i], new DoubleExpr(vals[i]));
			}
		}
		return subst;
	}

}
